import PubSub.*;

import org.omg.CORBA.*;
import org.omg.PortableServer.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;

import java.util.*;
import java.util.regex.*;

public class SubscriberImpl extends SubscriberPOA {
	public static final int PUSH = 1;
	public static final int PULL = 2;
	public static final int HOW_MANY = 1000;
	
	private Map<String,Topic> knownTopic;
	private List<Message> messages;
	private Credential credential;
	private String host;
	private String port;
	private ORB orb;
	private POA rootpoa;
	private NamingContextExt rootContext;
	private NamingContextExt topicContext;
	private NamingContextExt clientContext;
	private Listener lis;
	private int logic;
	private Broker broker;
	
	public SubscriberImpl(String username, String password, String host, String port) {
		knownTopic = new HashMap<String,Topic>();
		messages = new ArrayList<Message>();
		credential = new Credential(username, password);
		this.host = host;
		this.port = port;
	}
	

	public void newMessage(Message message) {
		if(logic==PUSH) {
			printMessage(message);
		} else if(logic==PULL) {
			messages.add(message);
		} else {
			System.out.println("ERROR: unknown message notification logic. Push will be used from now on");
			logic = PUSH;
		}
	}
	
	public Credential getCredential() {
		return credential;
	}
	
	public boolean bindClient() {
		try {
			System.out.println("Connecting...");
			
			org.omg.CORBA.Object objRef;
			Properties props = new Properties();
			
			// create and initialize the ORB
			props.put("org.omg.CORBA.ORBInitialHost", host);
			props.put("org.omg.CORBA.ORBInitialPort", port);
			orb = ORB.init(new String[0], props);

			// resolve the broker in naming
			objRef = orb.string_to_object("corbaname::"+host+":"+port+"#Broker");
			broker = BrokerHelper.narrow(objRef);

			// get reference to rootpoa & activate the POAManager
			rootpoa = POAHelper.narrow(orb.resolve_initial_references("RootPOA"));
			rootpoa.the_POAManager().activate();

			rootContext = NamingContextExtHelper.narrow(orb.resolve_initial_references("NameService"));
			topicContext = NamingContextExtHelper.narrow(rootContext.resolve(rootContext.to_name("Topic")));
			clientContext = NamingContextExtHelper.narrow(rootContext.resolve(rootContext.to_name("Client")));

			String key = broker.getHash(credential.username+credential.password);
			objRef = rootpoa.servant_to_reference(this);
			Subscriber clientPath = SubscriberHelper.narrow(objRef);
			NameComponent[] client = rootContext.to_name(key);
			clientContext.bind(client, clientPath);
			return true;
		} catch(AlreadyBound e) {
			System.out.println("ERROR: "+credential.username+" already bound in context");
			return false;
		} catch(Exception e) {
			System.out.println("ERROR: "+e.getMessage());
			return false;
		}
	}
	
	public void unbindClient() {
		try {
			System.out.println("Disconnecting...");
			String key = broker.getHash(credential.username+credential.password);
			clientContext.unbind(rootContext.to_name(key));
			lis.stopListening();
		} catch(NotFound e) {
			System.out.println("ERROR: "+credential.username+" not found in context");
		} catch(Exception e) {
			System.out.println("ERROR: "+e.getMessage());
		}
	}
	
	public void startListening(int logic) {
		try {
			this.logic = logic;
			lis = new Listener(orb);
			lis.start();
		} catch(Exception e) { 
			System.out.println("ERROR: "+e.getMessage());
		}
	}
	
	public String insert(String text, String insert, int period) {
	    Pattern p = Pattern.compile("(.{" + period + "})", Pattern.DOTALL);
	    Matcher m = p.matcher(text);
	    return m.replaceAll("$1" + insert);
	}
	
	public void printMessage(Message message) {
		System.out.print(
			"\n\t/"+
			"\n\t| TOPIC:   "+message.topic+
			"\n\t| AUTHOR:  "+message.author+
			"\n\t| DATE:    "+message.date+
			"\n\t| MESSAGE: "+insert(message.text, "\n\t|          ", 27)+
			"\n\t\\-------------------------------------/"
		);
	}
	
	public Topic getTopic(String topicName) {
		if(knownTopic.containsKey(topicName)) {
			return knownTopic.get(topicName);
		} else {
			// resolve the topic in naming
			org.omg.CORBA.Object topicRef = orb.string_to_object("corbaname::"+host+":"+port+"#Topic/"+topicName);
			Topic topic = TopicHelper.narrow(topicRef);
			knownTopic.put(topicName, topic);
			return topic;
		}
	}
	
	public List<String> getTopicList() {
		List<String> topics = new ArrayList<String>();
		BindingListHolder bl = new BindingListHolder();
		BindingIteratorHolder blIt = new BindingIteratorHolder();
		try {
			topicContext.list(HOW_MANY, bl, blIt);
			Binding[] bindings = bl.value;
			if(bindings.length==0) {
				System.out.println("\tNo topic in use");
			} else {
				int index;
				for(int i=0; i<bindings.length; i++) {
					index = bindings[i].binding_name.length-1;
					// check to see if this is not a naming context
					if(bindings[i].binding_type!=BindingType.ncontext) {
						topics.add(bindings[i].binding_name[index].id);
					}
				}
			}
		} catch(Exception e) {
			System.out.println("ERROR: "+e.getMessage());
		}
		return topics;
	}
	
	public void printTopicList() {
		List<String> topics = getTopicList();
		for(String topicName : topics) {
			System.out.println("\t- "+topicName);
		}
	}
	
	public void addTopic(String topicName) {
		try {
			System.out.println("Creating topic "+topicName+"...");
			broker.addTopic(topicName);
		} catch(Exception e) {
			System.out.println("ERROR: "+e.getMessage());
		}
	}
	
	public void removeTopic(String topicName) {
		try {
			System.out.println("Removing topic "+topicName+"...");
			broker.removeTopic(topicName);
		} catch(Exception e) {
			System.out.println("ERROR: "+e.getMessage());
		}
	}
	
	public void subscribeTo(String topicName) {
		try {
			Topic topic = getTopic(topicName);
			topic.addSubscriber(credential);
		} catch(Exception e) {
			System.out.println("ERROR: "+e.getMessage());
		}
	}
	
	public void unsubscribeFrom(String topicName) {
		try {
			Topic topic = getTopic(topicName);
			topic.removeSubscriber(credential);
		} catch(Exception e) {
			System.out.println("ERROR: "+e.getMessage());
		}
	}
	
	public void getSubscriptions() {
		List<String> topics = getTopicList();
		if(topics.size()==0) {
			System.out.println("\tNo subscribed topic");
		} else {
			Topic topic;
			org.omg.CORBA.Object objRef;
			for(String topicName : topics) {
				topic = getTopic(topicName);
				if(topic.hasSubscriber(credential)) {
					System.out.println("\t- "+topicName);
				}
			}
		}
	}
	
	public void addMessage(String topicName, String text) {
		try {
			Topic topic = getTopic(topicName);
			topic.addMessage(text, credential);
		} catch(Exception e) {
			System.out.println("ERROR: "+e.getMessage());
		}
	}
	
	public void getMessageInTopic(String topicName) {
		try {
			Topic topic = getTopic(topicName);
			Message[] messages = topic.getMessageList(credential);
			if(messages.length==0) {
				System.out.println("\t"+topicName+" empty");
			} else {
				for(int i=0; i<messages.length; i++) {
					printMessage(messages[i]);
				}
			}
		} catch(Exception e) {
			System.out.println("ERROR: "+e.getMessage());
		}
	}
	
	public void getUnseenMessageInTopic(String topicName) {
		try {
			Topic topic = getTopic(topicName);
			Message[] messages = topic.getUnseenMessageList(credential);
			if(messages.length==0) {
				System.out.println("\tNo new message in "+topicName);
			} else {
				for(int i=0; i<messages.length; i++) {
					printMessage(messages[i]);
				}
			}
		} catch(Exception e) {
			System.out.println("ERROR: "+e.getMessage());
		}
	}
	
	public void pullReceivedMessage() {
		if(messages.size()>0) {
			for(Message m : messages) {
				printMessage(m);
			}
			messages.clear();
		} else {
			System.out.println("\tNo new message received");
		}
	}
	
}
